#include "glbReader.hpp"

#include <iostream>
#include <glm/gtc/type_ptr.hpp>

GLBReader::GLBReader()
{
    m_bufferChunk = nullptr;
}

GLBReader::~GLBReader()
{
    if( m_bufferChunk != nullptr )
    {
        delete [] m_bufferChunk;
        m_bufferChunk = nullptr;
    }
}

void GLBReader::ReadGLBFile( std::string path )
{
    std::fstream fs;
    fs.open( path, std::ios_base::in | std::ios_base::binary );
    if( !fs.is_open() )
    { throw GLTFFailedToOpen( path.c_str() ); }

    GLBHeader header;
    fs.read( (char*)&header, sizeof(header) );

    if( header.magic != 0x46546C67 )
    { throw GLTFFailedToOpen( path.c_str() ); }

    if( header.version != 2 )
    { throw GLTFVersionDismatch( path.c_str() ); }

    ChunkHeader chunkheader;
    while( !fs.read( (char*)&chunkheader, sizeof(ChunkHeader) ).eof() )
    {
        if( !M_ReadAChunk( chunkheader, fs ) ){ throw GLTFBadChunk( path.c_str() ); }
    }
    fs.close();
}

void GLBReader::GetNodeDetail( const nlohmann::json& nodeInfo, NodeDetail& res )
{
    res.name = nodeInfo["name"];
    auto iter = nodeInfo.find("mesh");
    res.meshIndex = iter == nodeInfo.end() ? UINT32_MAX : static_cast<uint32_t>(iter.value());

    const auto jtrans = nodeInfo.find("translation");
    glm::vec3 trans = jtrans == nodeInfo.end() ? glm::vec3() : M_ReadJsonVec3( *jtrans );
    const auto jrot = nodeInfo.find("rotation");
    glm::quat rot = jrot == nodeInfo.end() ? glm::quat() : M_ReadJsonQuat( *jrot );
    const auto jscl = nodeInfo.find("scale");
    glm::vec3 scl = jscl == nodeInfo.end() ? glm::vec3(1.0f) : M_ReadJsonVec3( *jscl );

    // Do right multiply to form trans matrix: trans * rot * scal
    res.locTrans = glm::translate( glm::mat4(1), trans );
    res.locTrans = res.locTrans * glm::mat4( rot );
    res.locTrans = glm::scale( res.locTrans, scl );
}

void GLBReader::FetchNodeMeshProp( const nlohmann::json& meshInfo, GLBBufferView* bvs, const uint32_t viewCnt, const uint32_t primitiveIndex )
{
    const nlohmann::json& accessors = m_jsonChunk["accessors"];
    const nlohmann::json& bufferviews = m_jsonChunk["bufferViews"];
    const nlohmann::json& primitive = meshInfo["primitives"][primitiveIndex];
    for( uint32_t i = 0; i < viewCnt; ++i )
    {
        bvs[i].ptr = nullptr;
        bvs[i].length = 0;

        uint32_t accessorIndex = UINT32_MAX;
        if( bvs[i].attribute == GLB_ATTR_INDICIES )
        { accessorIndex = primitive["indices"]; }
        else {
            const nlohmann::json& attribute = primitive["attributes"];
            if( bvs[i].attribute == GLB_ATTR_POSITION )
            { accessorIndex = attribute["POSITION"]; }
            else if( bvs[i].attribute == GLB_ATTR_NORMAL )
            { accessorIndex = attribute["NORMAL"]; }
            else if( bvs[i].attribute == GLB_ATTR_TEXCOORD_0 )
            { accessorIndex = attribute["TEXCOORD_0"]; }
        }
        const nlohmann::json& accessor = accessors[accessorIndex];
        const nlohmann::json& bufferview = bufferviews[ static_cast<uint32_t>(accessor["bufferView"]) ];
        bvs[i].type = M_MapingGLBDataType( accessor["type"] );
        bvs[i].ptr = m_bufferChunk + static_cast<uint32_t>(bufferview["byteOffset"]);
        bvs[i].length = bufferview["byteLength"];
        bvs[i].count = accessor["count"];
    }
}

bool GLBReader::M_ReadAChunk( const ChunkHeader& chunkHeader, std::fstream& fs )
{
    switch ( chunkHeader.type )
    {
    case 0x4E4F534A:
    {
        char buffer[chunkHeader.length + 1];
        fs.read( buffer, chunkHeader.length );
        buffer[chunkHeader.length] = '\0';
        std::string jsonstr(buffer);
        m_jsonChunk = nlohmann::json::parse( jsonstr );
    }
        break;
    case 0x004E4942:
    {
        m_bufferChunk = new char[chunkHeader.length];
        fs.read(m_bufferChunk, chunkHeader.length);
    }
        break;
    default:
        return false;
    }
    return true;
}